module.exports = Matrix;

var Point = require('./point');

/**
 * <pre>
 *  _        _
 * | a  c tx  |
 * | b  d ty  |
 * |_0  0  1 _|
 * </pre>
 * 创建一个用于二维仿射变换的矩阵。
 * concat、inverse、rotate、scale 和 translate 方法返回应用了变换后的新矩阵。这些方法不会直接修改原矩阵。
 * 如果没有参数调用，则返回单位矩阵。
 * @name Matrix
 * @param {Number} [a]
 * @param {Number} [b]
 * @param {Number} [c]
 * @param {Number} [d]
 * @param {Number} [tx]
 * @param {Number} [ty]
 * @constructor
 */
function Matrix(a, b, c, d, tx, ty) {
  this.a = a !== undefined ? a : 1;
  this.b = b || 0;
  this.c = c || 0;
  this.d = d !== undefined ? d : 1;
  this.tx = tx || 0;
  this.ty = ty || 0;
}

Matrix.prototype = {

  clone: function () {
    return new Matrix(
      this.a,
      this.b,
      this.c,
      this.d,
      this.tx,
      this.ty
    );
  },

 /**
 * 返回此矩阵与另一个矩阵相乘后的结果，合并两者的几何效果。
 * 如果此矩阵是 A，传入的矩阵是 B，则结果矩阵为 A x B。
 * http://mathworld.wolfram.com/MatrixMultiplication.html
 * @name concat
 * @methodOf Matrix#
 *
 * @param {Matrix} matrix 要将此矩阵乘以的另一个矩阵。
 * @returns 矩阵乘法的结果，一个新的矩阵。
 * @type Matrix
 */
concat: function(matrix) {
  return new Matrix(
    this.a * matrix.a + this.c * matrix.b,
    this.b * matrix.a + this.d * matrix.b,
    this.a * matrix.c + this.c * matrix.d,
    this.b * matrix.c + this.d * matrix.d,
    this.a * matrix.tx + this.c * matrix.ty + this.tx,
    this.b * matrix.tx + this.d * matrix.ty + this.ty
  );
},

/**
 * 给定预变换坐标空间中的一个点，返回该点在变换发生后的坐标。
 * 与使用 transformnew Point() 方法应用的标准变换不同，
 * deltaTransformnew Point() 方法的变换不考虑平移参数 tx 和 ty。
 * @name deltaTransformPoint
 * @methodOf Matrix#
 * @see #transformPoint
 *
 * @return 此矩阵变换后的新点，忽略 tx 和 ty。
 * @type Point
 */
  deltaTransformPoint: function (point) {
    return new Point(
      this.a * point.x + this.c * point.y,
      this.b * point.x + this.d * point.y
    );
  },

  /**
   * 返回矩阵的逆
   * http://mathworld.wolfram.com/MatrixInverse.html
   * @name inverse
   * @methodOf Matrix#
   *
   * @returns 返回矩阵的逆矩阵。
   * @type Matrix
   */
  inverse: function () {
    var determinant = this.a * this.d - this.b * this.c;
    // TODO： determinant 为 0 时，无法求逆矩阵
    return new Matrix(
      this.d / determinant,
      -this.b / determinant,
      -this.c / determinant,
      this.a / determinant,
      (this.c * this.ty - this.d * this.tx) / determinant,
      (this.b * this.tx - this.a * this.ty) / determinant
    );
  },

/**
 * 返回一个新矩阵，该矩阵是此矩阵乘以一个旋转矩阵的结果。
 * @name rotate
 * @methodOf Matrix#
 * @see Matrix.rotation
 *
 * @param {Number} theta 旋转角度，单位为弧度。
 * @param {Point} [aboutPoint] 旋转中心点。默认为 (0,0)。
 * @returns 旋转了指定角度的新矩阵。
 * @type Matrix
 */
rotate: function(theta, aboutPoint) {
  return this.concat(Matrix.rotation(theta, aboutPoint));
},

/**
 * 返回一个新矩阵，该矩阵是此矩阵乘以一个缩放矩阵的结果。
 * @name scale
 * @methodOf Matrix#
 * @see Matrix.scale
 *
 * @param {Number} sx 水平缩放比例。
 * @param {Number} [sy] 垂直缩放比例。
 * @param {Point} [aboutPoint] 缩放时保持不变的点。
 * @type Matrix
 */
scale: function(sx, sy, aboutPoint) {
  return this.concat(Matrix.scale(sx, sy, aboutPoint));
},

/**
 * 返回将 Matrix 对象表示的几何变换应用于指定点后的结果。
 * @name transformPoint
 * @methodOf Matrix#
 * @see #deltaTransformPoint
 *
 * @returns 应用变换后的新点。
 * @type Point
 */
  transformPoint: function (point) {
    return new Point(
      this.a * point.x + this.c * point.y + this.tx,
      this.b * point.x + this.d * point.y + this.ty
    );
  },

  /**
   * Translates the matrix along the x and y axes, as specified by the tx and ty parameters.
   * @name translate
   * @methodOf Matrix#
   * @see Matrix.translation
   *
   * @param {Number} tx The translation along the x axis.
   * @param {Number} ty The translation along the y axis.
   * @returns A new matrix with the translation applied.
   * @type Matrix
   */
  translate: function (tx, ty) {
    return this.concat(Matrix.translation(tx, ty));
  }
};

/**
 * 创建一个矩阵变换，对应于给定的旋转角度，围绕 (0,0) 或指定点进行旋转。
 * @see Matrix#rotate
 *
 * @param {Number} theta 旋转角度，单位为弧度。
 * @param {Point} [aboutPoint] 旋转中心点。默认为 (0,0)。
 * @returns 一个对应于给定旋转的矩阵变换。
 * @type Matrix
 */

Matrix.rotation = function (theta, aboutPoint) {
  var rotationMatrix = new Matrix(
    Math.cos(theta),
    Math.sin(theta),
    -Math.sin(theta),
    Math.cos(theta)
  );

  if (aboutPoint) {
    rotationMatrix =
      Matrix.translation(aboutPoint.x, aboutPoint.y).concat(
        rotationMatrix
      ).concat(
        Matrix.translation(-aboutPoint.x, -aboutPoint.y)
      );
  }

  return rotationMatrix;
};

/**
 * 返回一个矩阵，该矩阵对应于分别沿着 x 和 y 轴按照 sx 和 sy 缩放因子进行缩放。
 * 如果只提供一个参数，则矩阵在两个轴上均匀缩放。
 * 如果提供了可选的 aboutPoint 参数，则缩放围绕指定的点进行。
 * @see Matrix#scale
 *
 * @param {Number} sx 沿 x 轴缩放的比例，如果没有提供 sy 则在两个轴上均匀缩放。
 * @param {Number} [sy] 沿 y 轴缩放的比例。
 * @param {Point} [aboutPoint] 缩放发生的中心点。默认为 (0,0)。
 * @returns 表示按照 sx 和 sy 缩放的矩阵变换。
 * @type Matrix
 */
Matrix.scale = function (sx, sy, aboutPoint) {
  sy = sy || sx;

  var scaleMatrix = new Matrix(sx, 0, 0, sy);

  if (aboutPoint) {
    scaleMatrix =
      Matrix.translation(aboutPoint.x, aboutPoint.y).concat(
        scaleMatrix
      ).concat(
        Matrix.translation(-aboutPoint.x, -aboutPoint.y)
      );
  }

  return scaleMatrix;
};

/**
 * Returns a matrix that corresponds to a translation of tx, ty.
 * @see Matrix#translate
 *
 * @param {Number} tx The amount to translate in the x direction.
 * @param {Number} ty The amount to translate in the y direction.
 * @return A matrix transformation representing a translation by tx and ty.
 * @type Matrix
 */
Matrix.translation = function (tx, ty) {
  return new Matrix(1, 0, 0, 1, tx, ty);
};

/**
 * 表示单位矩阵的常量。
 * @name IDENTITY
 * @fieldOf Matrix
 */
Matrix.IDENTITY = new Matrix();
/**
 * 表示水平翻转变换矩阵的常量。
 * @name HORIZONTAL_FLIP
 * @fieldOf Matrix
 */
Matrix.HORIZONTAL_FLIP = new Matrix(-1, 0, 0, 1);
/**
 * 表示垂直翻转变换矩阵的常量。
 * @name VERTICAL_FLIP
 * @fieldOf Matrix
 */
Matrix.VERTICAL_FLIP = new Matrix(1, 0, 0, -1);
